<!DOCTYPE html>
<html>

<head>
    <title>棋谱复盘镜像</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="https://cdn.staticfile.net/twitter-bootstrap/5.1.1/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.staticfile.net/twitter-bootstrap/5.1.1/js/bootstrap.bundle.min.js"></script>
</head>

<body>
    <div class="container-fluid p-2">
        <div class="btn-group form-control">
            <input class="form-control bg-secondary" type="text" id="sgftext">
            <button type="button" class="form-control btn btn-primary" onclick="gt.main(event)">Show</button>
            <span id="posColorList" class="badge bg-info" style="width: 50px;">0</span>
            <button type="button" class="btn btn-primary" onclick="gt.main(event)">-</button>
            <input class="form-control" type="range" id="myRange" onchange="gt.main(event)" value="0" min="0" max="10">
            <button type="button" class="btn btn-primary" onclick="gt.main(event)">+</button>
            <button id="qp-mode" type="button" class="form-control btn btn-primary"
                onclick="gt.main(event)">下棋模式</button>
            <button id="man-auto" type="button" class="btn btn-primary" onclick="gt.main(event)">＞</button>
            <button type="button" class="form-control btn btn-primary" onclick="gt.main(event)">New</button>
            <button type="button" class="form-control btn btn-primary" onclick="gt.main(event)">记录</button>
            <button type="button" class="form-control btn btn-primary" onclick="gt.main(event)">步数</button>
        </div>
        <div id="baseid" class="col border">
            <canvas id="myCanvas" style="border:1px solid gray;"></canvas>
        </div>
    </div>
    <script>
        var health_alert = false;
        function health() {
            const now = new Date();
            const minutes = now.getMinutes();
            if (minutes === 0 || minutes === 30) {
                if (health_alert) {
                    alert("游戏健康忠告：请注意休息，合理安排游戏时间。");
                    health_alert = false;
                }
            } else {
                health_alert = true;
            }
            setTimeout("health()", 25000);
        }
        setTimeout("health()", 1000);
        class DrawQipan {
            constructor(canvasid, grid = 20) {
                this.grid = grid;
                this.gridHalf = grid / 2;
                this.qipanWidth = 20 * this.grid;
                this.canvas = document.getElementById(canvasid);
                this.canvas.width = this.qipanWidth;
                this.canvas.height = this.qipanWidth;
                this.ctx = this.create_ctx(this.canvas);
                this.positions = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s"];
                this.painting = false;
                this.blueList = ["rg", "oc"];
                this.setupClickListener(this.canvas, this);
                this.isShowText = true;
                this.strokeStyle = "B";
                this.draw({});
            }
            create_ctx(canvas) {
                let ctx = canvas.getContext("2d");
                ctx.strokeStyle = "black";
                ctx.lineWidth = 2;
                return ctx;
            }
            setupClickListener(element, someVariable) {
                element.addEventListener('mousedown', function (event) {
                    someVariable.painting = true;
                    someVariable.canvasListener("mousedown", event.clientX, event.clientY);
                });
                element.addEventListener('mousemove', function (event) {
                    if (someVariable.painting) {
                        someVariable.canvasListener("mousemove", event.clientX, event.clientY);
                    }
                });
                element.addEventListener('mouseup', function (event) {
                    if (someVariable.painting) {
                        someVariable.painting = false;
                        someVariable.canvasListener("mouseup", event.clientX, event.clientY);
                    }
                });
            }
            canvasListener(action, x, y) {
                let qpMode = document.getElementById("qp-mode").innerText;
                let rect, rx, ry, gr, gc, position;
                rect = this.canvas.getBoundingClientRect();
                position = this.coordinate_position(x, y);
                [rx, ry] = this.position_coordinate(position);
                if (qpMode == "下棋模式" && action == "mousedown") {
                    let sgf = this.strokeStyle + "[" + position + "]";
                } else if (qpMode == "选择模式" && action == "mousedown") {
                    this.startPosition = position;
                    this.painting = true;
                    this.selectedList = [position];
                    this.ctx.strokeStyle = "red";
                    this.ctx.lineWidth = 2;
                    this.ctx.beginPath();
                    this.ctx.moveTo(rx, ry);
                } else if (qpMode == "选择模式" && action == "mousemove") {
                    if (this.painting) {
                        if (position != this.selectedList[this.selectedList.length - 1]) {
                            this.selectedList.push(position);
                            this.ctx.lineTo(rx, ry);
                            this.ctx.stroke();
                        }
                    }
                } else if (qpMode == "选择模式" && action == "mouseup") {
                    this.painting = false;
                    let [sgf, pcDict, pcList] = this.select(this.selectedList);
                    sgo.newWeiQi(sgf, pcDict, pcList);
                    document.getElementById("myRange").value = 0;
                    document.getElementById("myRange").max = sgo.sgf.length - 1;
                    document.getElementById("posColorList").textContent = 0;
                    this.draw(sgo.posColor);
                }
            }
            select(selectedList) {
                if (selectedList[0] != selectedList[selectedList.length - 1]) {
                    selectedList.push(selectedList[0]);
                }
                let linshiList = [], blueList = [];
                sgo.sgf.forEach((item, index) => {
                    if (this.isPointInPolygon(item.slice(2, 4), selectedList)) {
                        linshiList.push(item.slice(2, 4));
                    }
                });
                selectedList.concat(linshiList).forEach((item, index) => {
                    blueList.includes(item) || blueList.push(item);
                });
                this.blueList = JSON.parse(JSON.stringify(blueList));
                let sgf = [], pcDict = {}, pcList = [];
                sgo.sgf.forEach((item, index) => {
                    if (blueList.includes(item.slice(2, 4))) {
                        sgf.push(item);
                        pcList.push(JSON.parse(JSON.stringify(sgo.posColorList[index])));
                    }
                });
                pcDict = JSON.parse(JSON.stringify(pcList[pcList.length - 1]));
                return [sgf, pcDict, pcList];
            }
            draw(posColor) {
                let cx, cy, current;
                let start = this.grid;
                let end = 19 * this.grid;
                let radius = Math.floor(this.grid / 3 - 1);
                this.ctx.clearRect(0, 0, this.qipanWidth, this.qipanWidth);
                this.ctx.lineWidth = 2;
                this.ctx.strokeStyle = "black";
                this.ctx.beginPath();
                this.positions.forEach((row, ri) => {
                    current = (this.positions.indexOf(row) + 1) * this.grid;
                    this.ctx.moveTo(current, start);
                    this.ctx.lineTo(current, end);
                    this.ctx.moveTo(start, current);
                    this.ctx.lineTo(end, current);
                });
                let pos = ["d", "j", "p"];
                for (let row in pos) {
                    for (let col in pos) {
                        [cx, cy] = this.position_coordinate(pos[row] + pos[col]);
                        this.ctx.moveTo(cx + radius, cy);
                        this.ctx.arc(cx, cy, radius, 0, 2 * Math.PI);
                    }
                }
                this.ctx.stroke();
                let strokeStyle = { B: "black", W: "White" };
                for (let pos in posColor) {
                    this.ctx.beginPath();
                    [cx, cy] = this.position_coordinate(pos);
                    this.ctx.moveTo(cx + Math.floor(this.grid / 2 - 1), cy);
                    this.ctx.lineWidth = 3;
                    if (pos == sgo.sgf[Number(document.getElementById("myRange").value)].slice(2, 4)) {
                        this.ctx.strokeStyle = "Gold";
                    } else if (this.blueList.includes(pos)) {
                        this.ctx.strokeStyle = "LightBlue";
                    } else {
                        this.ctx.strokeStyle = "black";
                        this.ctx.lineWidth = 2;
                    }
                    this.ctx.arc(cx, cy, Math.floor(this.grid / 2 - 1), 0, 2 * Math.PI);
                    this.ctx.fillStyle = strokeStyle[posColor[pos][0]];
                    this.ctx.fill();
                    if (this.isShowText) {
                        if (posColor[pos][0] == "B") {
                            this.ctx.fillStyle = "White";
                        } else {
                            this.ctx.fillStyle = "black";
                        }
                        this.ctx.textBaseline = "middle";
                        this.ctx.fillText(posColor[pos][1], cx - this.ctx.measureText(posColor[pos][1]).width / 2, cy);
                    }
                    this.ctx.stroke();
                }
                this.ctx.strokeStyle = "black";
            }
            coordinate_position(x, y) {
                let rect, row, col;
                rect = this.canvas.getBoundingClientRect();
                row = this.minmax(Math.floor((x + this.grid / 2 - rect.left) / this.grid) - 1);
                col = this.minmax(Math.floor((y + this.grid / 2 - rect.top) / this.grid) - 1);
                return this.positions[row] + this.positions[col];
            }
            minmax(coordinate) {
                return Math.min(18, Math.max(0, coordinate));
            }
            position_coordinate(position) {
                let row, col, rect, rx, cy;
                rect = this.canvas.getBoundingClientRect();
                row = this.positions.indexOf(position.slice(0, 1));
                col = this.positions.indexOf(position.slice(1, 2));
                rx = Math.floor((row + 1) * this.grid);
                cy = Math.floor((col + 1) * this.grid);
                return [rx, cy];
            }
            canvas_size(qp_row, qp_col) {
                document.getElementById("myCanvas").width = this.qipanWidth;
                document.getElementById("myCanvas").height = this.qipanWidth;
            }
            isPointInPolygon(point, polygon) {
                let numIntersections = 0;
                let [x, y] = this.position_coordinate(point);
                const n = polygon.length;
                for (let i = 0, j = n - 1; i < n; j = i++) {
                    let [xi, yi] = this.position_coordinate(polygon[i]);
                    let [xj, yj] = this.position_coordinate(polygon[j]);
                    if ((yi > y) != (yj > y) &&
                        x < (xj - xi) * (y - yi) / (yj - yi) + xi) {
                        numIntersections++;
                    }
                }
                return numIntersections % 2 !== 0;
            }
        }
        const drawQipan = new DrawQipan("myCanvas", 23);

        class simpleGo {
            constructor() {
                this.positions = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s"];
                this.newWeiQi([], {}, []);
                this.goString = { string: [], empty: [] };
            }
            go_handler(item = "B[qd]") {
                var position = item.slice(2, 4);
                if (this.posColor.hasOwnProperty(position)) {
                    return false;
                } else {
                    this.posColor[position] = [item[0]];
                }
                var del_pos = [];
                var near_pos = this.near_positions(position);
                for (var npn in near_pos) {
                    if (this.posColor.hasOwnProperty(near_pos[npn])) {
                        if (this.posColor[near_pos[npn]][0] != this.posColor[position][0]) {
                            this.goString = { 'string': [], 'empty': [] };
                            this.go_string(near_pos[npn]);
                            if (this.goString['empty'].length == 0) {
                                for (var spn in this.goString['string']) {
                                    del_pos.includes(this.goString['string'][spn]) || del_pos.push(this.goString['string'][spn]);
                                }
                            }
                        }
                    }
                }
                if (del_pos.length == 0) {
                    this.goString = { 'string': [], 'empty': [] };
                    this.go_string(position);
                    if (this.goString['empty'].length == 0) {
                        delete this.posColor[position];
                        return false;
                    }
                } else {
                    for (var d_p in del_pos) {
                        delete this.posColor[del_pos[d_p]];
                    }
                }
                this.sgf.push(item);
                this.posColor[position].push(this.sgf.length)
                this.posColorList.push(JSON.parse(JSON.stringify(this.posColor)));
                return true;
            }
            go_string(position) {
                this.goString['string'].push(position);
                var nPos = this.near_positions(position);
                for (var np in nPos) {
                    if (this.posColor.hasOwnProperty(nPos[np])) {
                        if (!this.goString['string'].includes(nPos[np]) && this.posColor[nPos[np]][0] == this.posColor[position][0]) {
                            this.go_string(nPos[np]);
                        }
                    } else {
                        this.goString['empty'].includes(nPos[np]) || this.goString['empty'].push(nPos[np]);
                    }
                }
            }
            near_positions(position) {
                var near_pos = [];
                var row = this.near(position[0]);
                var col = this.near(position[1]);
                for (var r in row) {
                    near_pos.push(row[r] + position[1]);
                }
                for (var c in col) {
                    near_pos.push(position[0] + col[c]);
                }
                return near_pos;
            }
            near(char) {
                switch (char) {
                    case "a":
                        return ["b"];
                    case "s":
                        return ["r"];
                    case "b": case "c": case "d": case "e": case "f": case "g":
                    case "h": case "i": case "j": case "k": case "l": case "m":
                    case "n": case "o": case "p": case "q": case "r":
                    default:
                        var pos = this.positions.indexOf(char);
                        return [this.positions[pos - 1], this.positions[pos + 1]];
                }
            }
            newWeiQi(sgf, posColor, posColorList) {
                this.sgf = sgf;
                this.posColor = posColor;
                this.posColorList = posColorList;
            }
        }
        const sgo = new simpleGo();
        class Tools {
            constructor() {
                this.positions = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s"];
                this.combinedPattern = /[\u4e00-\u9fa5a-zA-Z0-9;\[\]\-： ]+/g;
                this.sgf = [];
                this.blueList = [];
                this.goldDict = {};
                this.mirror_area = null;
                //this.test();
            }
            main(event) {
                switch (event.target.textContent) {
                    case "New":
                        sgo.newWeiQi([], {}, []);
                        drawQipan.draw({});
                        break;
                    case "Show":
                        drawQipan.blueList = [];
                        sgo.newWeiQi([], {}, []);
                        if (document.getElementById("sgftext").value == "") {
                            egm.handle(sgftext)
                        } else {
                            egm.handle(document.getElementById("sgftext").value)
                        }
                        document.getElementById("myRange").max = sgo.sgf.length - 1;
                        document.getElementById("myRange").value = sgo.sgf.length - 1;
                        document.getElementById("posColorList").textContent = Number(document.getElementById("myRange").value) + 1;
                        drawQipan.draw(sgo.posColor);
                        break;
                    case "-":
                        document.getElementById("myRange").value = Math.max(0, Number(document.getElementById("myRange").value) - 1);
                        drawQipan.draw(sgo.posColorList[Number(document.getElementById("myRange").value)]);
                        break;
                    case "+":
                        document.getElementById("myRange").value = Math.min(sgo.sgf.length - 1, Number(document.getElementById("myRange").value) + 1);
                        drawQipan.draw(sgo.posColorList[Number(document.getElementById("myRange").value)]);
                        break;
                    case "＞":
                        this.autoRun = true;
                        setTimeout("gt.autorunning(0)", 2000);
                        break;
                    case "记录":
                        break;
                    case "步数":
                        drawQipan.isShowText ? drawQipan.isShowText = false : drawQipan.isShowText = true;
                        break;
                    case "下棋模式":
                        event.target.textContent = "选择模式";
                        break;
                    case "选择模式":
                        event.target.textContent = "下棋模式";
                        break;
                    default:
                        drawQipan.draw(sgo.posColorList[Number(document.getElementById("myRange").value)]);
                        break;
                }
            }
            autorunning(n) {
                if (n < sgo.posColorList.length) {
                    document.getElementById("myRange").value = n;
                    document.getElementById("posColorList").textContent = n;
                    drawQipan.draw(sgo.posColorList[n++]);
                    this.autoRun && setTimeout("gt.autorunning(" + n + ")", 1000);
                } else {
                    this.autoRun = false;
                }
            }
        }
        const gt = new Tools();

        const sgftext = `(;CA[utf8]AP[golaxy]GN[29届三星车险杯半决赛]PW[党毅飞]PB[连笑]WR[3002]BR[3002]HA[0]RE[W+R]KM[6.5]SZ[19]ST[0]RU[japanese]
;B[qd];W[dp];B[cd];W[qp];B[cq];W[cp];B[dq];W[fq];B[eq];W[ep];B[fr];W[gq];
B[gr];W[hq];B[od];W[ec];B[jc];W[dg];B[de];W[hc];B[dc];W[ed];B[ee];W[he];
B[ci];W[lc];B[je];W[le];B[ic];W[eh];B[hb];W[gc];B[jg];W[hg];B[md];W[ld];
B[lg];W[ne];B[nd];W[me];B[og];W[mg];B[mh];W[lh];B[li];W[kh];B[kg];W[nh];
B[mi];W[ng];B[ii];W[hi];B[hj];W[gi];B[cf];W[cg];B[oj];W[ni];B[nj];W[ki];
B[ih];W[kj];B[lk];W[kk];B[ll];W[ik];B[ij];W[pi];B[gj];W[ej];B[hh];W[gh];
B[fj];W[km];B[fi];W[dj];B[gg];W[ff];B[fh];W[fg];B[pj];W[nb];B[ob];W[pb];
B[oc];W[pg];B[qi];W[qh];B[bg];W[bh];B[bf];W[ri];B[qj];W[lm];B[mm];W[mn];
B[nm];W[nn];B[om];W[on];B[pn];W[po];B[qn];W[hr];B[kl];W[jl];B[jm];W[im];
B[jn];W[ln];B[pq];W[qq];B[mq];W[or];B[hl];W[il];B[ho];W[in];B[io];W[jo];
B[jp];W[kn];B[fo];W[fp];B[iq];W[jr];B[gn];W[kp];B[ch];W[cj];B[bj];W[cl];
B[qo];W[pp];B[rg];W[rh];B[oi];W[oh];B[of];W[oe];B[re];W[qf];B[rf];W[oa];
B[pe];W[qc];B[rc];W[rb];B[pf];W[sc];B[ph];W[kb];B[qg];W[jb];B[el];W[bq];
B[br];W[bp];B[ir];W[is];B[ib];W[ar];B[gs];W[es];B[er];W[hs];B[ds];W[cs];
B[cr];W[dr];B[dn];W[cn];B[ds];W[eb];B[rd];W[pc];B[pd];W[mc];B[rp];W[rq];
B[db];W[dr];B[bi];W[ei];B[ds];W[da];B[ca];W[dr];B[sb];W[sa];B[ds];W[ea];
B[cb];W[dr];B[sd];W[sb];B[ds];W[lj];B[mj];W[dr];B[qa];W[pa];B[ds];W[ml];
B[mk];W[dr];B[nc];W[mb];B[ds];W[pm];B[pl];W[dr];B[as];W[aq];B[ds];W[sp];
B[ro];W[dr];B[qb];W[ra];B[ds];W[qm];B[rm];W[dr];B[qb];W[qa];B[ds];W[ql];
B[rl];W[dr];B[ef];W[eg];B[ds];W[if];B[jf];W[dr];B[na];W[ma];B[ds];W[id];
B[jd];W[dr];B[ko];W[lo];B[ds];W[so];B[qk];W[dr];B[dm];W[bk];B[ds];W[gm];
B[hm];W[dr];B[cm];W[bm];B[ds];W[hn];B[gl];W[dr];B[gf];W[fe];B[ds];W[eo];
B[fm];W[dr];B[bn];W[co];B[ds];W[hp];B[bs];W[ip];B[sn];W[sq];B[hf];W[ge];
B[ig];W[go];B[ie];W[gb];B[fn];W[hd];B[ek];W[ak])`
        class extract_go_manual {
            constructor() {
                this.positions = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s"];
                this.combinedPattern = /[\u4e00-\u9fa5a-zA-Z0-9;\[\]\-： ]+/g;
            }
            handle(text) {
                sgo.newWeiQi([], {}, []);
                var results = text.match(this.combinedPattern) || [];
                var format_content = results.join('').split(";");
                for (var index in format_content) {
                    var item = format_content[index];
                    if (item.length == 5 && this.positions.includes(item[2]) && this.positions.includes(item[3])) {
                        sgo.go_handler(item);
                    }
                }
            }
        }
        const egm = new extract_go_manual();

    </script>
</body>

</html>